package auth

import (
	"context"
	"coolcar/shared/auth/token"
	"coolcar/shared/id"
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"google.golang.org/grpc/metadata"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"github.com/dgrijalva/jwt-go"

	"google.golang.org/grpc"
)

const (
	authorizationHeader = "authorization"
	bearerPrefix        = "Bearer "
)

func Interceptor(publicKeyFile string) (grpc.UnaryServerInterceptor, error) {
	f, err := os.Open(publicKeyFile)
	if err != nil {
		return nil, fmt.Errorf("cannnot open public key file: %v", err)
	}

	b, err := ioutil.ReadAll(f)
	if err != nil {
		return nil, fmt.Errorf("cannot read public key: %v", err)
	}

	pubKey, err := jwt.ParseRSAPublicKeyFromPEM(b)
	if err != nil {
		return nil, fmt.Errorf("canot parse public key: %v", err)
	}
	i := &interceptor{
		verifier: &token.JWTTokenVerifier{
			PublicKey: pubKey,
		},
	}
	return i.HandleRequest, nil
}

type TokenVerifier interface {
	Verify(token string) (string, error)
}
type interceptor struct {
	verifier TokenVerifier
}

func (i *interceptor) HandleRequest(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
	tkn, err := tokenFromContext(ctx)
	if err != nil {
		return nil, status.Error(codes.Unauthenticated, "")
	}

	accountId, err := i.verifier.Verify(tkn)
	if err != nil {
		return nil, status.Errorf(codes.Unauthenticated, "token  not valid:%v", err)
	}

	return handler(ContextWithAccountID(ctx, id.AccountID(accountId)), req)
}

type accountIDKey struct {
}

func ContextWithAccountID(ctx context.Context, accountID id.AccountID) context.Context {
	return context.WithValue(ctx, accountIDKey{}, accountID)
}
func AccountIDFromContext(ctx context.Context) (id.AccountID, error) {
	accountID, ok := ctx.Value(accountIDKey{}).(id.AccountID)
	if !ok {
		return "", status.Error(codes.Unauthenticated, "no accountID")
	}
	return accountID, nil
}
func tokenFromContext(ctx context.Context) (string, error) {
	md, b := metadata.FromIncomingContext(ctx)
	if !b {
		return "", status.Error(codes.Unauthenticated, "no metadata")
	}
	var bearerslic []string
	for _, s := range md.Get(authorizationHeader) {
		if strings.HasPrefix(s, bearerPrefix) {
			bearerslic = strings.Split(s, " ")
			break
		}
	}
	if len(bearerslic) < 2 {
		return "", status.Error(codes.Unauthenticated, "no token")
	}
	return bearerslic[1], nil
}
